/*******************************************************************************
 * File name:     gpdma.c
 * Description: 
 * Project:       Gpdma
 * Target:        LPC2478
 * Compiler:      arm-none-eabi-gcc
 * Date:          22-08-2011
 * Author:        kuba-pc
 * Based on:      ---
 ******************************************************************************/

/*==============================================================================
 Includes
==============================================================================*/
# include <stdbool.h>
# include "LPC24xx.h"
# include "hdr_scb.h"
# include "typedef.h"
# include "irq.h"

# include "gpdma.h"

/*==============================================================================
 Defines
==============================================================================*/
# define GPDMA_CHANNELS       2
# define BURST_NUM            8
# define WIDTH_NUM            3
# define PERIPH_CONNECTIONS   16
# define FLOW_CTRL            8
/*==============================================================================
 Types
==============================================================================*/
struct gpdmaService {
   void (*transferEndService)(void); /* user service at the end of transfer */
   void (*errorService)(void);       /* user service in case of error */
};

struct gpdmaChannel {
   uint32 ctrl;
   uint32 conf;
   uint32 src;
   uint32 dst;
};

struct confLut {                          /* configuration look up tables */
   uint32 srcBurst[BURST_NUM];            /* source burst look up table */
   uint32 dstBurst[BURST_NUM];            /* destination burst look up table */
   uint32 srcWidth[WIDTH_NUM];            /* source width look up table */
   uint32 dstWidth[WIDTH_NUM];            /* destination width look up table */
   uint32 srcPeriph[PERIPH_CONNECTIONS];  /* source periheral look up table */
   uint32 dstPeriph[PERIPH_CONNECTIONS];  /* destination periheral look up table */
   uint32 trsfrType[FLOW_CTRL];           /* transfer type and flow control */
};

/*==============================================================================
 Globals
==============================================================================*/
static struct gpdmaService serviceArray[GPDMA_CHANNELS];
static struct gpdmaChannel channelArray[GPDMA_CHANNELS];
static const struct confLut confLut = {
      .srcBurst = { GPDMA_CH_CTRL_SB_SIZE_1,
            GPDMA_CH_CTRL_SB_SIZE_4,
            GPDMA_CH_CTRL_SB_SIZE_8,
            GPDMA_CH_CTRL_SB_SIZE_16,
            GPDMA_CH_CTRL_SB_SIZE_32,
            GPDMA_CH_CTRL_SB_SIZE_64,
            GPDMA_CH_CTRL_SB_SIZE_128,
            GPDMA_CH_CTRL_SB_SIZE_256 },
      .dstBurst = { GPDMA_CH_CTRL_DB_SIZE_1,
            GPDMA_CH_CTRL_DB_SIZE_4,
            GPDMA_CH_CTRL_DB_SIZE_8,
            GPDMA_CH_CTRL_DB_SIZE_16,
            GPDMA_CH_CTRL_DB_SIZE_32,
            GPDMA_CH_CTRL_DB_SIZE_64,
            GPDMA_CH_CTRL_DB_SIZE_128,
            GPDMA_CH_CTRL_DB_SIZE_256 },
      .srcWidth = { GPDMA_CH_CTRL_SWIDTH_8,
            GPDMA_CH_CTRL_SWIDTH_16,
            GPDMA_CH_CTRL_SWIDTH_32 },
      .dstWidth = { GPDMA_CH_CTRL_DWIDTH_8,
            GPDMA_CH_CTRL_DWIDTH_16,
            GPDMA_CH_CTRL_DWIDTH_32 },
      .srcPeriph = { GPDMA_CH_CFG_SRC_PERIPH_SSP0TX,
            GPDMA_CH_CFG_SRC_PERIPH_SSP0RX,
            GPDMA_CH_CFG_SRC_PERIPH_SSP1TX,
            GPDMA_CH_CFG_SRC_PERIPH_SSP1RX,
            GPDMA_CH_CFG_SRC_PERIPH_SD,
            GPDMA_CH_CFG_SRC_PERIPH_I2S0,
            GPDMA_CH_CFG_SRC_PERIPH_I2S1 },
      .dstPeriph = { GPDMA_CH_CFG_DST_PERIPH_SSP0TX,
            GPDMA_CH_CFG_DST_PERIPH_SSP0RX,
            GPDMA_CH_CFG_DST_PERIPH_SSP1TX,
            GPDMA_CH_CFG_DST_PERIPH_SSP1RX,
            GPDMA_CH_CFG_DST_PERIPH_SD,
            GPDMA_CH_CFG_DST_PERIPH_I2S0,
            GPDMA_CH_CFG_DST_PERIPH_I2S1 },
      .trsfrType = { GPDMA_CH_CFG_FLOW_CTRL_M2M_DMA,
            GPDMA_CH_CFG_FLOW_CTRL_M2P_DMA,
            GPDMA_CH_CFG_FLOW_CTRL_P2M_DMA,
            GPDMA_CH_CFG_FLOW_CTRL_SP2DP_DMA,
            GPDMA_CH_CFG_FLOW_CTRL_SP2DP_DP,
            GPDMA_CH_CFG_FLOW_CTRL_M2P_P,
            GPDMA_CH_CFG_FLOW_CTRL_P2M_P,
            GPDMA_CH_CFG_FLOW_CTRL_SP2DP_SP }
};
/*==============================================================================
 Static ISR prototypes
==============================================================================*/
static void gpdmaIsr (void) __attribute__ ((interrupt("IRQ")));

/*==============================================================================
 Static function prototypes
==============================================================================*/
//static void createList (struct list *list, const uint32 ctrl, const struct gpdmaConf *gpdmaConf);
static void dummyService (void);

/*==============================================================================
 Global function definitions
==============================================================================*/
/*------------------------------------------------------------------------------
 function name:   gpdmaInit
 description:     gpdma module initialization
 parameters:      interrupt priority
 returned value:  none
------------------------------------------------------------------------------*/
bool gpdmaInit (enum priority priority)
{
   /* power for GPDMA */
   PCONP |= PCONP_PCGPDMA;
   /* clear Terminal Count interrupts for channels 0 and 1 */
   GPDMA_INT_TCCLR = (GPDMA_INT_TCCLR_INT_TCCLR0 | GPDMA_INT_TCCLR_INT_TCCLR1);
   /* clear Error interrupts for channels 0 and 1 */
   GPDMA_INT_ERR_CLR = (GPDMA_INT_ERR_CLR_INT_ERR0 | GPDMA_INT_ERR_CLR_INT_ERR1);
   /* enable GPDMA, and set little-endian */
   GPDMA_CONFIG = GPDMA_CONFIG_ENABLE;
   /* wait till GPDMA is enabled */
   while (!(GPDMA_CONFIG & GPDMA_CONFIG_ENABLE));
   /* set GPDMA interrupt in VIC */
   return irqInit(irqGPDMA, (void *)gpdmaIsr, priority);
}

/*------------------------------------------------------------------------------
 function name:   gpdmaChnl0Init
 description:     channel 0 initialization
 parameters:      pointer to configuration struct
 returned value:  channel settings
------------------------------------------------------------------------------*/
gpdmaChannel gpdmaChnl0Init (const struct gpdmaConf *gpdmaConf)
{
   gpdmaChannel tmp;

   /* remember channel settings */
   tmp = &channelArray[0];

   /* set function pointers in case they are 0 */
   if (gpdmaConf->transferEndService == 0)
   {
      serviceArray[0].transferEndService = dummyService;
   }
   else
   {
      serviceArray[0].transferEndService = gpdmaConf->transferEndService;
   }

   if (gpdmaConf->errorService == 0)
   {
      serviceArray[0].errorService = dummyService;
   }
   else
   {
      serviceArray[0].errorService = gpdmaConf->errorService;
   }

   /* set control word */
   switch (gpdmaConf->mode)
   {
   case gpdmaM2M:
      tmp->ctrl = confLut.srcBurst[gpdmaConf->sbSize] | confLut.dstBurst[gpdmaConf->dbSize]
                  | confLut.srcWidth[gpdmaConf->srcWidth] | confLut.dstWidth[gpdmaConf->dstWidth]
                  | (GPDMA_CH_CTRL_SI) | (GPDMA_CH_CTRL_DI) | (GPDMA_CH_CTRL_I);
      break; /* end of gpdmaM2M */

   case gpdmaM2P:
      tmp->ctrl = confLut.srcBurst[gpdmaConf->sbSize] | confLut.dstBurst[gpdmaConf->dbSize]
                  | confLut.srcWidth[gpdmaConf->srcWidth] | confLut.dstWidth[gpdmaConf->dstWidth]
                  | (GPDMA_CH_CTRL_SI) | (GPDMA_CH_CTRL_I);
      break; /* end of gpdmaM2P */

   case gpdmaP2M:
      tmp->ctrl = confLut.srcBurst[gpdmaConf->sbSize] | confLut.dstBurst[gpdmaConf->dbSize]
                  | confLut.srcWidth[gpdmaConf->srcWidth] | confLut.dstWidth[gpdmaConf->dstWidth]
                  | (GPDMA_CH_CTRL_DI) | (GPDMA_CH_CTRL_I);
      break; /* end of gpdmaP2M */

   case gpdmaP2P:
      tmp->ctrl = confLut.srcBurst[gpdmaConf->sbSize] | confLut.dstBurst[gpdmaConf->dbSize]
                  | confLut.srcWidth[gpdmaConf->srcWidth] | confLut.dstWidth[gpdmaConf->dstWidth]
                  | (GPDMA_CH_CTRL_I);
      break;            /* end of gpdmaP2P */

   default:
      tmp->ctrl = 0;
      break;   /* wrong mode */
   }

   tmp->conf = confLut.srcPeriph[gpdmaConf->srcPeriph] | confLut.dstPeriph[gpdmaConf->dstPeriph]
               | confLut.trsfrType[gpdmaConf->flowCtrl] | GPDMA_CH_CFG_ITC;
   tmp->src = gpdmaConf->srcAddr;
   tmp->dst = gpdmaConf->dstAddr;

   return tmp;
}

/*------------------------------------------------------------------------------
 function name:   gpdmaChnl1Init
 description:     channel 1 initialization
 parameters:      pointer to configuration struct
 returned value:  channel settings
------------------------------------------------------------------------------*/
gpdmaChannel gpdmaChnl1Init (const struct gpdmaConf *gpdmaConf)
{
   gpdmaChannel tmp;

   /* remember channel settings */
   tmp = &channelArray[1];

   /* set function pointers in case they are 0 */
   if (gpdmaConf->transferEndService == 0)
   {
      serviceArray[1].transferEndService = dummyService;
   }
   else
   {
      serviceArray[1].transferEndService = gpdmaConf->transferEndService;
   }

   if (gpdmaConf->errorService == 0)
   {
      serviceArray[1].errorService = dummyService;
   }
   else
   {
      serviceArray[1].errorService = gpdmaConf->errorService;
   }

   /* set control word */
   switch (gpdmaConf->mode)
   {
   case gpdmaM2M:
      tmp->ctrl = confLut.srcBurst[gpdmaConf->sbSize] | confLut.dstBurst[gpdmaConf->dbSize]
                  | confLut.srcWidth[gpdmaConf->srcWidth] | confLut.dstWidth[gpdmaConf->dstWidth]
                  | (GPDMA_CH_CTRL_SI) | (GPDMA_CH_CTRL_DI) | (GPDMA_CH_CTRL_I);
      break; /* end of gpdmaM2M */

   case gpdmaM2P:
      tmp->ctrl = confLut.srcBurst[gpdmaConf->sbSize] | confLut.dstBurst[gpdmaConf->dbSize]
                  | confLut.srcWidth[gpdmaConf->srcWidth] | confLut.dstWidth[gpdmaConf->dstWidth]
                  | (GPDMA_CH_CTRL_SI) | (GPDMA_CH_CTRL_I);
      break; /* end of gpdmaM2P */

   case gpdmaP2M:
      tmp->ctrl = confLut.srcBurst[gpdmaConf->sbSize] | confLut.dstBurst[gpdmaConf->dbSize]
                  | confLut.srcWidth[gpdmaConf->srcWidth] | confLut.dstWidth[gpdmaConf->dstWidth]
                  | (GPDMA_CH_CTRL_DI) | (GPDMA_CH_CTRL_I);
      break; /* end of gpdmaP2M */

   case gpdmaP2P:
      tmp->ctrl = confLut.srcBurst[gpdmaConf->sbSize] | confLut.dstBurst[gpdmaConf->dbSize]
                  | confLut.srcWidth[gpdmaConf->srcWidth] | confLut.dstWidth[gpdmaConf->dstWidth]
                  | (GPDMA_CH_CTRL_I);
      break;            /* end of gpdmaP2P */

   default:
      tmp->ctrl = 0;
      break;   /* wrong mode */
   }

   tmp->conf = confLut.srcPeriph[gpdmaConf->srcPeriph] | confLut.dstPeriph[gpdmaConf->dstPeriph]
               | confLut.trsfrType[gpdmaConf->flowCtrl] | GPDMA_CH_CFG_ITC;
   tmp->src = gpdmaConf->srcAddr;
   tmp->dst = gpdmaConf->dstAddr;

   return tmp;
}

/*------------------------------------------------------------------------------
 function name:   gpdmaChnl0Enable
 description:     enable channel 0
 parameters:      channel settings, size
 returned value:  none
------------------------------------------------------------------------------*/
void gpdmaChnl0Enable (const gpdmaChannel gpdmaChnl, const uint16 size)
{
   /* clear pending interrupt in channel 0 */
   GPDMA_INT_TCCLR = GPDMA_INT_TCCLR_INT_TCCLR0;
   /* clear pending errors in channel 0 */
   GPDMA_INT_ERR_CLR = GPDMA_INT_ERR_CLR_INT_ERR0;
   /* set source */
   GPDMA_CH0_SRC = gpdmaChnl->src;
   /* set destination */
   GPDMA_CH0_DEST = gpdmaChnl->dst;
   /* no list */
   GPDMA_CH0_LLI = 0;
   /* set control word */
   GPDMA_CH0_CTRL = (gpdmaChnl->ctrl) | (size & GPDMA_CH_CTRL_TRANSFER_SIZE_mask);
   /* set channel configuration */
   GPDMA_CH0_CFG = gpdmaChnl->conf;
   /* enable channel */
   GPDMA_CH0_CFG |= GPDMA_CH_CFG_CHANNEL_EN;
}

/*------------------------------------------------------------------------------
 function name:   gpdmaChnl1Enable
 description:     enable channel 1
 parameters:      channel settings, size
 returned value:  none
------------------------------------------------------------------------------*/
void gpdmaChnl1Enable (const gpdmaChannel gpdmaChnl, const uint16 size)
{
   /* clear pending interrupt in channel 1 */
   GPDMA_INT_TCCLR = GPDMA_INT_TCCLR_INT_TCCLR1;
   /* clear pending errors in channel 1 */
   GPDMA_INT_ERR_CLR = GPDMA_INT_ERR_CLR_INT_ERR1;
   /* set source */
   GPDMA_CH1_SRC = gpdmaChnl->src;
   /* set destination */
   GPDMA_CH1_DEST = gpdmaChnl->dst;
   /* no list */
   GPDMA_CH1_LLI = 0;
   /* set control word */
   GPDMA_CH1_CTRL = (gpdmaChnl->ctrl) | (size & GPDMA_CH_CTRL_TRANSFER_SIZE_mask);
   /* set channel configuration */
   GPDMA_CH1_CFG = gpdmaChnl->conf;
   /* enable channel */
   GPDMA_CH1_CFG |= GPDMA_CH_CFG_CHANNEL_EN;
}

# if 0
/*------------------------------------------------------------------------------
 function name:   gpdmaListInit
 description:     init channels for linked list transfer
 parameters:      pointer to struct list, pointer to const struct gpdmaConf
 returned value:  gpdmaChannel object
------------------------------------------------------------------------------*/
gpdmaChannel gpdmaListInit (struct list *list, const struct gpdmaConf *gpdmaConf)
{
   gpdmaChannel ch;
   uint32 ctrl;

   /* set channel */
   switch (gpdmaConf->channel)
   {
   case gpdmaCH0:
      ch = &channelArray[0];
      ch->src  = (volatile uint32 *)& GPDMA_CH0_SRC;
      ch->dst  = (volatile uint32 *)& GPDMA_CH0_DEST;
      ch->lli  = (volatile uint32 *)& GPDMA_CH0_LLI;
      ch->ctrl = (volatile uint32 *)& GPDMA_CH0_CTRL;
      ch->conf = (volatile uint32 *)& GPDMA_CH0_CFG;
      ch->intrptClear = GPDMA_INT_TCCLR_INT_TCCLR0;
      ch->errClear = GPDMA_INT_ERR_CLR_INT_ERR0;
      break;

   case gpdmaCH1:
      ch = &channelArray[1];
      ch->src  = (volatile uint32 *)& GPDMA_CH1_SRC;
      ch->dst  = (volatile uint32 *)& GPDMA_CH1_DEST;
      ch->lli  = (volatile uint32 *)& GPDMA_CH1_LLI;
      ch->ctrl = (volatile uint32 *)& GPDMA_CH1_CTRL;
      ch->conf = (volatile uint32 *)& GPDMA_CH1_CFG;
      ch->intrptClear = GPDMA_INT_TCCLR_INT_TCCLR1;
      ch->errClear = GPDMA_INT_ERR_CLR_INT_ERR1;
      break;

   default:
      ch = (gpdmaChannel)NULL;
      break;
   }

   /* set list control word */
   switch (gpdmaConf->mode)
   {
   case gpdmaM2M:
      ctrl = (gpdmaConf->transferSize & GPDMA_CH_CTRL_TRANSFER_SIZE_mask)
                        | (gpdmaConf->sbSize) | (gpdmaConf->dbSize)
                        | (gpdmaConf->srcWidth) | (gpdmaConf->dstWidth)
                        | (GPDMA_CH_CTRL_SI) | (GPDMA_CH_CTRL_DI) | (GPDMA_CH_CTRL_I);
      break; /* end of gpdmaM2M */

   case gpdmaM2P:
      ctrl = (gpdmaConf->transferSize & GPDMA_CH_CTRL_TRANSFER_SIZE_mask)
                        | (gpdmaConf->sbSize) | (gpdmaConf->dbSize)
                        | (gpdmaConf->srcWidth) | (gpdmaConf->dstWidth)
                        | (GPDMA_CH_CTRL_SI) | (GPDMA_CH_CTRL_I);
      break; /* end of gpdmaM2P */

   case gpdmaP2M:
      ctrl = (gpdmaConf->transferSize & GPDMA_CH_CTRL_TRANSFER_SIZE_mask)
                        | (gpdmaConf->sbSize) | (gpdmaConf->dbSize)
                        | (gpdmaConf->srcWidth) | (gpdmaConf->dstWidth)
                        | (GPDMA_CH_CTRL_DI) | (GPDMA_CH_CTRL_I);
      break; /* end of gpdmaP2M */

   case gpdmaP2P:
      ctrl = (gpdmaConf->transferSize & GPDMA_CH_CTRL_TRANSFER_SIZE_mask)
                        | (gpdmaConf->sbSize) | (gpdmaConf->dbSize)
                        | (gpdmaConf->srcWidth) | (gpdmaConf->dstWidth)
                        | (GPDMA_CH_CTRL_I);
      break;            /* end of gpdmaP2P */

   default:
      ch = (gpdmaChannel)NULL;
      break;   /* wrong mode */
   }

   ch->srcPeriph = gpdmaConf->srcPeriph;
   ch->dstPeriph = gpdmaConf->dstPeriph;
   ch->flowCtrl = gpdmaConf->flowCtrl;

   /* set pointers to dummyService */
   if (gpdmaConf->transferEndService == 0)
   {
      ch->transferEndService = dummyService;
   }
   else
   {
      ch->transferEndService = gpdmaConf->transferEndService;
   }
   if (gpdmaConf->errorService == 0)
   {
      ch->transferEndService = dummyService;
   }
   else
   {
      ch->errorService = gpdmaConf->errorService;
   }

   /* fill list */
   createList(list, ctrl, gpdmaConf);

   /* write list first element to channel registers */
   *ch->src = list[0].srcAddr;         /* source address */
   *ch->dst = list[0].dstAddr;         /* destination address */
   *ch->lli = (uint32)list[0].next;    /* next item */
   *ch->ctrl = list[0].ctrl;           /* set channel control word */
   return ch;
}

/*------------------------------------------------------------------------------
 function name:   gpdmaStartListTrsfr
 description:     starts GPDMA transfer using linked list
 parameters:      gpdmaChannel object
 returned value:  none
------------------------------------------------------------------------------*/
void gpdmaStartListTrsfr (const gpdmaChannel gpdmaChannel)
{
   /* clear pending interrupts in channel 0 */
   GPDMA_INT_TCCLR = gpdmaChannel->intrptClear;
   GPDMA_INT_ERR_CLR = gpdmaChannel->errClear;

   /* enable channel */
   *gpdmaChannel->conf = (GPDMA_CH_CFG_CHANNEL_EN | (gpdmaChannel->srcPeriph) | (gpdmaChannel->dstPeriph)
                     | (gpdmaChannel->flowCtrl) | GPDMA_CH_CFG_IE | GPDMA_CH_CFG_ITC);
}

/*==============================================================================
 Static function definitions
==============================================================================*/
/*------------------------------------------------------------------------------
 function name:   createList
 description:     create GPDMA linked list
 parameters:      pointer to const struct gpdmaConf, GPDMA control word
 returned value:  none
------------------------------------------------------------------------------*/
void createList (struct list *list, const uint32 ctrl, const struct gpdmaConf *gpdmaConf)
{
   uint32 i;

   /* create list */
   for (i = 0; i < (gpdmaConf->listItems - 1); ++i)
   {
      list[i].srcAddr  = gpdmaConf->listSrcAddr[i];   /* source address */
      list[i].dstAddr  = gpdmaConf->listDstAddr[i];   /* destination address */
      list[i].next     = &list[i + 1];                /* next item address */
      list[i].ctrl     = ctrl;                        /* control word */
   }

   /* the last iteration could be checked in the above for, in every pass- it's slow */
   list[gpdmaConf->listItems - 1].srcAddr  = gpdmaConf->listSrcAddr[i];
   list[gpdmaConf->listItems - 1].dstAddr  = gpdmaConf->listDstAddr[i];
   list[gpdmaConf->listItems - 1].next     = 0;              /* the last one must be zero */
   list[gpdmaConf->listItems - 1].ctrl     = ctrl;
}
# endif
/*==============================================================================
 Static ISR definitions
==============================================================================*/
/*------------------------------------------------------------------------------
 function name:   gpdmaIsr
 description:     GPDMA ISR
 parameters:      none
 returned value:  none
------------------------------------------------------------------------------*/
static void gpdmaIsr (void)
{
   uint32 reg = GPDMA_INT_TCSTAT;

   /* terminal count interrupt- transfer completed */
   if (reg & GPDMA_INT_TCSTAT_INT_TCSTATUS0)
   {
      GPDMA_INT_TCCLR = GPDMA_INT_TCSTAT_INT_TCSTATUS0;    /* clear interrupt */
      serviceArray[0].transferEndService();                 /* call user service */
   }

   if (reg & GPDMA_INT_TCSTAT_INT_TCSTATUS1)
   {
      GPDMA_INT_TCCLR = GPDMA_INT_TCSTAT_INT_TCSTATUS1;    /* clear interrupt */
      serviceArray[1].transferEndService();                 /* call user service */
   }

   /* error interrupt */
   reg = GPDMA_INT_ERR_STAT;
   if (reg & GPDMA_INT_ERR_CLR_INT_ERR0)
   {
      GPDMA_INT_ERR_CLR = GPDMA_INT_ERR_CLR_INT_ERR0;      /* clear interrupt */
      serviceArray[0].errorService();                       /* call user service */
   }

   if (reg & GPDMA_INT_ERR_CLR_INT_ERR1)
   {
      GPDMA_INT_ERR_CLR = GPDMA_INT_ERR_CLR_INT_ERR1;      /* clear interrupt */
      serviceArray[1].errorService();                       /* call user service */
   }

   VICVectAddr = 0;                 /* interrupt acknowledgment */
}

/*------------------------------------------------------------------------------
 function name:   dummyService
 description:     does nothing
 parameters:      none
 returned value:  none
------------------------------------------------------------------------------*/
static void dummyService (void)
{
   return;
}
/******************************************************************************
* END OF FILE
******************************************************************************/

